• 数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别。

  • 隔离级别有以下四种:

    • 读未提交(Read Uncommitted)
    • 读已提交(Read Committed)大多数数据库默认的隔离级别
    • 可重复读(Repeatable-Read)mysql数据库默认的级别
    • 序列化(serializable)
  • 看下面这个例子,A修改事务级别为未提交读,并开始事务,对user表做一次查询:

    B事务更新一条记录:

    此时B事务还未提交,A在事务内做一次查询,发现查询结果已经改变:

    B进行事务回滚:

    A再做一次查询,查询结果又变回去了:

    由试验可知,在一个进程的事务当中,我更改了其中的一行数据,但是我修改完之后就释放了锁,这时候另一个进程读取了该数据,此时先前的事务是还未提交的,直到我回滚了数据,另一个进程读的数据就变成了无用的或是错误的数据,我们通常把这种数据称为脏数据,这种情况读出来的数据叫脏读。

  • 之前是只要操作完数据就立刻释放掉锁,现在是把释放锁的位置调整到事务提交之后,此时在事务提交之前,其他进程是无法对该行数据进行读取的,包括任何操作。那么数据库为此种状态的数据库操作规则又给了一个名字叫“读已提交(Read Committed)”,或者叫不可重复读。

  • 把隔离性调整为READ-COMMITTED(读取提交内容),设置A的事务隔离级别,并进入事务做一次查询:

    B开始事务,并对记录进行修改:

    A再对user表进行查询,发现记录没有受到影响:

    B提交事务:

    A再对user表进行查询,发现记录被修改:

    试验进行到这里,你会发现,在同一个事务中如果两次读取相同的数据时,最后的结果却不一致。这里我们把这种现象称为:不可重复读。因为在第一个事务读取了数据之后,此时另一个事务把该数据给修改了,这时候事务提交,那么另一个事务在第二次读取的时候,结果就不一样,一个修改前的,一个是修改后的。

    但是细心的你会发现,既然你说此种隔离性是在事务提交后才释放锁,那么在试验过程中,在该数据未提交前,另一个事务为什么也是仍然可以读取的呀。是我说错了吗?不是的,在这里mysql使用了一个并发版本控制机制,他们把它叫做MVCC,通俗的也就是说:mysql为了提高系统的并发量,在事务未提交前,虽然事务内操作的数据是锁定状态,但是另一个事务仍然可以读取,大多数数据库默认的就是这个级别的隔离性。但mysql不是。

    而且不只是在更新数据时出现这个问题,在插入数据时仍然会造成类似的这样一种现象:mysql虽然锁住了正在操作的数据行,但它仍然不会阻止另一个事务往表插入新行新的数据。比如:一个事务读取或更新了表里的所有行,接着又有另一个事务往该表里插入一个新行,在事务提交后。原来读取或更改过数据的事务又第二次读取了相同的数据,这时候这个事务中两次读取的结果集行数就不一样。原来更新了所有行,而现在读出来发现竟然还有一行没有更新。这就是所谓的幻读。

    为了防止同事务中两次读取数据不一致,(包括不可重读和幻读),接下来该如何继续做呢?!

    mysql依然采取的是MVCC并发版本控制来解决这个问题。具体是:如果事务中存在多次读取同样的数据,MySQL第一次读的时候仍然会保持选择读最新提交事务的数据,当第一次之后,之后再读时,mysql会取第一次读取的数据作为结果。这样就保证了同一个事务多次读取数据时数据的一致性。这时候,mysql把这种解决方案叫做:可重复读(Repeatable-Read),也就是上述所写的第三个隔离性,也是mysql默认的隔离级别。

    注意:幻读和不可重复读(Read Committed)都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

    说到这里,真的就完事了吗?到这里其实mysql并未完全解决数据的一致性问题。只是在读取上做了手脚,解决了传统意义上的幻读和不可重复读。
    例子:1 A事务开启,B事务开启。
    ​ 2 B事务往表里面插入了一条数据,但还并未提交。
    ​ 3 A事务开始查询了,并没有发现B事务这次插入的数据。然后此时B事务提交了数据。
    ​ 4 于是乎,A事务就以为没有这条数据,就开始添加这条数据,但是却发现,发生了数据 重复冲突。

    最后这个时候,该我们的最后一种隔离级别也是最高的隔离级:别序列化(serializable)登场了。
    该隔离级别会自动在锁住你要操作的整个表的数据,如果另一个进程事务想要操作表里的任何数据就需要等待获得锁的进程操作完成释放锁。可避免脏读、不可重复读、幻读的发生。当然性能会下降很多,会导致很多的进程相互排队竞争锁。

    后记:以上所说的四种隔离性的锁机制应用是数据库自动完成的,不需要人为干预。隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效

  • 本文转自数据库事务原子性、一致性是怎样实现的?